//
// Created on 2024/1/2.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "ohos_egl_render_context.h"

#include <string.h>
#include "rtc_base/logging.h"

namespace webrtc {
bool CheckEglExtension(const char *extensions, const char *extension)
{
    size_t extLen = strlen(extension);
    const char *end = extensions + strlen(extensions);

    while (extensions < end) {
        size_t n = 0;
        /* Skip whitespaces, if any */
        if (*extensions == ' ') {
            extensions++;
            continue;
        }

        n = strcspn(extensions, " ");
        /* Compare strings */
        if (n == extLen && strncmp(extension, extensions, n) == 0) {
            return true; /* Found */
        }
        extensions += n;
    }
    /* Not found */
    return false;
}

#define CASE_EGL_STR(value)      \
    case value:                  \
        return #value
const char *GetEglErrorString()
{
    EGLint error = eglGetError();
    switch (error) {
        CASE_EGL_STR(EGL_SUCCESS);
        CASE_EGL_STR(EGL_NOT_INITIALIZED);
        CASE_EGL_STR(EGL_BAD_ACCESS);
        CASE_EGL_STR(EGL_BAD_ALLOC);
        CASE_EGL_STR(EGL_BAD_ATTRIBUTE);
        CASE_EGL_STR(EGL_BAD_CONTEXT);
        CASE_EGL_STR(EGL_BAD_CONFIG);
        CASE_EGL_STR(EGL_BAD_CURRENT_SURFACE);
        CASE_EGL_STR(EGL_BAD_DISPLAY);
        CASE_EGL_STR(EGL_BAD_SURFACE);
        CASE_EGL_STR(EGL_BAD_MATCH);
        CASE_EGL_STR(EGL_BAD_PARAMETER);
        CASE_EGL_STR(EGL_BAD_NATIVE_PIXMAP);
        CASE_EGL_STR(EGL_BAD_NATIVE_WINDOW);
        CASE_EGL_STR(EGL_CONTEXT_LOST);
    default:
        return "Unknow Error";
    }
}
#undef CASE_EGL_STR


constexpr const char EGL_KHR_SURFACELESS_CONTEXT[] = "EGL_KHR_surfaceless_context";

using GetPlatformDisplayExt = PFNEGLGETPLATFORMDISPLAYEXTPROC;
constexpr const char *EGL_GET_PLATFORM_DISPLAY_EXT = "eglGetPlatformDisplayEXT";
static EGLDisplay GetPlatformEglDisplay(EGLenum platform, void *native_display, const EGLint *attrib_list) {
//     static GetPlatformDisplayExt eglGetPlatformDisplayExt = NULL;
//
//     if (!eglGetPlatformDisplayExt) {
//         const char *extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
//         if (extensions) {
//             eglGetPlatformDisplayExt = (GetPlatformDisplayExt)eglGetProcAddress(EGL_GET_PLATFORM_DISPLAY_EXT);
//         }
//     }
//
//     if (eglGetPlatformDisplayExt) {
//         return eglGetPlatformDisplayExt(platform, native_display, attrib_list);
//     }

    return eglGetDisplay((EGLNativeDisplayType)native_display);
}

EGLRenderContext::EGLRenderContext() {}

EGLRenderContext::~EGLRenderContext() noexcept
{
    if (eglDisplay_ == EGL_NO_DISPLAY) {
        return;
    }

    eglDestroyContext(eglDisplay_, eglContext_);
    eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglTerminate(eglDisplay_);
    eglReleaseThread();

    eglDisplay_ = EGL_NO_DISPLAY;
    eglContext_ = EGL_NO_CONTEXT;
}

bool EGLRenderContext::Init(EGLContext eglContext)
{
    if (IsEglContextReady()) {
        return true;
    }

    eglDisplay_ = GetPlatformEglDisplay(EGL_PLATFORM_OHOS_KHR, EGL_DEFAULT_DISPLAY, NULL);
    if (eglDisplay_ == EGL_NO_DISPLAY) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::Init: failed to create eglDisplay, error:"<<GetEglErrorString();
        return false;
    }

    EGLint major, minor;
    if (eglInitialize(eglDisplay_, &major, &minor) == EGL_FALSE) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::Init: Failed to initialize EGLDisplay, error:"<<GetEglErrorString();
        return false;
    }
    SetupEglExtensions();

    if (eglBindAPI(EGL_OPENGL_ES_API) == EGL_FALSE) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::Init: Failed to bind OpenGL ES API, error:"<<GetEglErrorString();
        return false;
    }
    EGLint count;
    EGLint configAttribs[] = {EGL_SURFACE_TYPE,
                              EGL_WINDOW_BIT,
                              EGL_RED_SIZE,
                              8,
                              EGL_GREEN_SIZE,
                              8,
                              EGL_BLUE_SIZE,
                              8,
                              EGL_ALPHA_SIZE,
                              8,
                              EGL_RENDERABLE_TYPE,
                              EGL_OPENGL_ES3_BIT,
                              EGL_NONE};
    if (eglChooseConfig(eglDisplay_, configAttribs, &config_, 1, &count) == EGL_FALSE) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::Init: Failed to bind choose config, error:"<<GetEglErrorString();
        return false;
    }
    const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    eglContext_ = eglCreateContext(eglDisplay_, config_, eglContext, contextAttribs);
    if (eglContext_ == EGL_NO_CONTEXT) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::Init: Failed to bind create context, error:"<<GetEglErrorString();
        return false;
    }

    return true;
}

void EGLRenderContext::SetupEglExtensions()
{
    const char *extensions = eglQueryString(eglDisplay_, EGL_EXTENSIONS);
    if (extensions == nullptr) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::SetupEglExtensions: not extensions.";
        return;
    }
    RTC_LOG_T(LS_INFO)<<"EGLRenderContext extensions:"<<extensions;
    hasEglSurfacelessContext_ = CheckEglExtension(extensions, EGL_KHR_SURFACELESS_CONTEXT);

    // now WGR product's EGL support these extensions but they are not in the EGL_EXTENSIONS lists.
    hasEglBufferAge_ = true;
    hasEglPartialUpdate_ = true;
    eglSetDamageRegionFunc_ =
        reinterpret_cast<PFNEGLSETDAMAGEREGIONKHRPROC>(eglGetProcAddress("eglSetDamageRegionKHR"));
    eglSwapBuffersWithDamageFunc_ =
        reinterpret_cast<PFNEGLSWAPBUFFERSWITHDAMAGEKHRPROC>(eglGetProcAddress("eglSwapBuffersWithDamageKHR"));

    // for shm optimize
    eglCreateImageFunc_ = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
    eglImageTargetTexture2DOESFunc_ =
        reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
}

void EGLRenderContext::MakeCurrent(EGLSurface surface) const
{
    EGLSurface currSurface = surface;
    if (currSurface == EGL_NO_SURFACE) {
        if (!eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
            RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::MakeCurrent: Failed to make no current, error:"<<GetEglErrorString();
        }
    } else if (!eglMakeCurrent(eglDisplay_, currSurface, currSurface, eglContext_)) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, surface, EGL_CONFIG_ID, &surfaceId);
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::MakeCurrent: Failed to make current on EGLSurface:"<<surfaceId<<", error:"<<GetEglErrorString();
    }
}

void EGLRenderContext::SwapBuffers(EGLSurface surface) const
{
    EGLBoolean ret = eglSwapBuffers(eglDisplay_, surface);
    if (ret == EGL_FALSE) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, surface, EGL_CONFIG_ID, &surfaceId);
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::SwapBuffers: Failed to SwapBuffers on EGLSurface:"<<surfaceId<<", error:"<<GetEglErrorString();
    }
}

void EGLRenderContext::DestroyEglSurface(EGLSurface surface)
{
    if (!eglDestroySurface(eglDisplay_, surface)) {
        EGLint surfaceId = -1;
        eglQuerySurface(eglDisplay_, surface, EGL_CONFIG_ID, &surfaceId);
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::DestroyEglWindowSurface: Failed to DestroySurface on EGLSurface:"<<surfaceId<<", error:"<<GetEglErrorString();
    }
}

EGLSurface EGLRenderContext::CreateEglSurface(EGLNativeWindowType eglNativeWindow, const EGLint *attribList)
{
    if (!IsEglContextReady()) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::CreateEglWindowSurface: EGL context has not initialized.";
        return EGL_NO_SURFACE;
    }

    eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

    EGLSurface surface = eglCreateWindowSurface(eglDisplay_, config_, eglNativeWindow, attribList);
    if (surface == EGL_NO_SURFACE) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::CreateEglWindowSurface: Failed to create eglSurface, error:"<<GetEglErrorString();
        return EGL_NO_SURFACE;
    }

    return surface;
}

EGLSurface EGLRenderContext::CreatePBufferEglSurface(const EGLint *attribList)
{
    if (!IsEglContextReady()) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::CreatePBufferEglSurface: EGL context has not initialized.";
        return EGL_NO_SURFACE;
    }

    eglMakeCurrent(eglDisplay_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

    EGLSurface surface = eglCreatePbufferSurface(eglDisplay_, config_, attribList);
    if (surface == EGL_NO_SURFACE) {
        RTC_LOG_T(LS_ERROR)<<"EGLRenderContext::CreatePBufferEglSurface: Failed to create eglSurface, error:"<<GetEglErrorString();
        return EGL_NO_SURFACE;
    }

    return surface;
}
} // namespace webrtc